package com.example.tianling.WebSocket.handler;

import com.example.tianling.common.componment.XfXhStreamClient;
import com.example.tianling.common.utils.RedisUtils;
import com.example.tianling.common.utils.ScheduledTask;
import com.example.tianling.common.utils.WsMessage;
import com.example.tianling.config.XfXhConfig;
import com.example.tianling.entity.*;
import com.example.tianling.service.Impl.*;
import com.example.tianling.service.MatchService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import org.junit.platform.commons.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

@Component
public class StructureTrainWebSocketHandler implements WebSocketHandler {
    private static final Logger logger = LoggerFactory.getLogger(StructureTrainWebSocketHandler.class);
    private static final Gson gson = new GsonBuilder().create();
    private static UserServiceImpl userService;
    private static RoomServiceImpl roomService;
    private static BookServiceImpl bookService;
    private static RedisUtils redisUtils;
    private static MatchService matchService;
    private static ScheduledTask scheduledTask;
    private static TrainResultServiceImpl trainResultService;

    // 房间信息
    private static final Map<String, RoomEntity> roomInfo = new HashMap<>();
    private static final Map<String, List<RoomUserEntity>> roomUser = new HashMap<>();
    private static final Map<String, BookEntity> book = new HashMap<>();


    // 训练结果
    private static final Map<String, Integer> resultNum = new HashMap<>();
    private static final Map<String, Map<String, String>> bookList = new HashMap<>();


    // 保存session
    private static final Map<String, WebSocketSession> userRegistry = new ConcurrentHashMap<>();
    private static final Map<String, Set<WebSocketSession>> roomRegistry = new ConcurrentHashMap<>();

    /*
     * 进入房间
     * 1.房间不存在
     *    a.创建房间，并添加session
     *    b.添加房主: roomVo和数据表room中的房主userId
     * 2.房间存在
     *    a.用户第一次进入房间
     *       1).增加roomVo的roomUser和realNumber
     *       2).增加数据表room的realNumber和roomUser中的用户
     *    b.用户重连
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        String roomId = (String) session.getAttributes().get("roomId");

        logger.info("用户{} 进入房间{}", userId, roomId);

        logger.info(userRegistry.toString());

        AtomicBoolean isReconnect = new AtomicBoolean(false);

        // 1.房间不存在
        if (!roomRegistry.containsKey(roomId)) {
            // a.创建房间，并添加session
            userRegistry.put(userId, session);
            Set<WebSocketSession> set = new HashSet<>();
            set.add(session);
            roomRegistry.put(roomId, set);

            // b.添加房主: roomVo和数据表room中的房主userId
            RoomEntity roomEntity = roomService.getRoom(Integer.parseInt(roomId));
            roomEntity.setUserId(Integer.parseInt(userId));
            roomInfo.put(roomId,roomEntity);

            List<RoomUserEntity> list = new ArrayList<>();
            roomUser.put(roomId, list);
        } else {
            userRegistry.put(userId, session);
            roomRegistry.get(roomId).add(session);

        }
        logger.info(userRegistry.toString());

        roomUser.get(roomId).forEach(
                x -> {
                    if (userId.equals(x.getUserId().toString())) {
                        isReconnect.set(true);}
                }
        );
        RoomUserEntity roomUserEntity = createRoomUser(roomId, userId);
        if (!isReconnect.get()) {
            logger.info("用户{}成功进入", userId);
            roomInfo.get(roomId).setRealNumber(roomInfo.get(roomId).getRealNumber() + 1);

            roomUser.get(roomId).add(roomUserEntity);
        }
        // 发送信息
        Map<String, Object> registerMap = new HashMap<>();
        registerMap.put("roomInfo", roomInfo.get(roomId));
        registerMap.put("roomUsers", roomUser.get(roomId));

        WsMessage<Object> registerMessage = new WsMessage<>("register", registerMap);
        sendMessageToAll(roomId, registerMessage);
        sendSystemMessage(roomId, roomUserEntity.getUserName()+"进入房间");

        logger.info(roomInfo.get(roomId).toString());
        logger.info(roomUser.get(roomId).toString());
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        // 获取session中的roomId
        String userId = (String) session.getAttributes().get("userId");
        String roomId = (String) session.getAttributes().get("roomId");

        // 获取message中的发送用户from、接收用户target、信息类型type
        JsonObject json = gson.fromJson((String) message.getPayload(), JsonObject.class);   // json解析message
        String type = json.get("type").getAsString();
        logger.info("用户 {} {}", userId, type);
        UserEntity user = userService.getUserByUserId(Integer.parseInt(userId));

        switch (type)
        {
            case "ping":
                WsMessage<Object> pong = new WsMessage<>("pong");
                sendMessageToOne(userId, pong);
                break;
            case "logout":
                /*
                 * 用户退出房间事件
                 * 1.更新roomVo: realNumber-1,删除roomUser
                 * 2.删除rooms中对应的session
                 * 3.向房间所有用户发送logout信息
                 * 4.更新room、roomUser表: realNumber-1,删除roomUser
                 * 5.如果real_number=0,则从room中删除room
                 */
                // 未开始前的退出

                // 2.删除rooms中对应的session
//                userRegistry.remove(userId);
//                roomRegistry.get(roomId).remove(session);

                Integer realNumber = roomInfo.get(roomId).getRealNumber();
                roomInfo.get(roomId).setRealNumber(realNumber - 1);
                roomUser.get(roomId).removeIf(x -> userId.equals(x.getUserId().toString()));

                if (roomInfo.get(roomId).getRoomState() == 0) {
                    // c.房间还在匹配中，则减少match_type的realNumber
                    MatchEntity match = redisUtils.get("match_" + roomInfo.get(roomId).getType(), MatchEntity.class);
                    if (match != null && roomId.equals(match.getRoomId().toString())) {
                        match.setRealNumber(roomInfo.get(roomId).getRealNumber());
                        redisUtils.set("match_" + roomInfo.get(roomId).getType(), match, 3 * 60 * 60);
                    } else {
                        // d.房间不在匹配，则减少train_match表中的realNumber
                        matchService.updateRealNumber(Integer.parseInt(roomId), roomInfo.get(roomId).getRealNumber());
                    }

                    // e.如果realNumber == 0，删除roomVo,room,train_match
                    if (roomInfo.get(roomId).getRealNumber() == 0) {
                        roomService.removeRoom(Integer.parseInt(roomId));
                        matchService.removeByRoomId(Integer.parseInt(roomId));
                        // 1)房间还在匹配中,更新match_type
                        if (match != null && roomId.equals(match.getRoomId().toString())) {
                            MatchEntity newMatch = matchService.getMatch(roomInfo.get(roomId).getType());
                            redisUtils.set("match_" + roomInfo.get(roomId).getType(), newMatch, 3 * 60 * 60);
                        }
                    }
                }

                // 3.向房间所有用户发送logout信息
                Map<String, Object> logoutMap = new HashMap<>();
                logoutMap.put("roomInfo", roomInfo.get(roomId));
                logoutMap.put("roomUsers", roomUser.get(roomId));

                WsMessage<Object> logoutMessage = new WsMessage<>(type, logoutMap);
                sendMessageToAll(roomId, logoutMessage);
                sendSystemMessage(roomId, user.getUserName()+"退出房间");

                if (roomInfo.get(roomId).getRealNumber() == 0) { clear(roomId); }

                break;

            case "change":
                /*
                 * 交换房主权限
                 * 1.更新redis: 修改房主userId
                 * 2.向房间所有用户发送信息
                 * 3.更新room、match表中的userId
                 */
                // 1.更新redis: 取出redis、修改房主、存入redis
                Integer targetUserId = json.get("target").getAsInt();
                RoomUserEntity targetRoomUser = null;
                for (RoomUserEntity roomUserEntity : roomUser.get(roomId)) {
                    if (targetUserId.equals(roomUserEntity.getUserId())) {
                        targetRoomUser = roomUserEntity;
                    }
                }
                if (targetRoomUser == null) {
                    WsMessage<Object> changeFailMessage = new WsMessage<>("changeFail");
                    sendMessageToAll(roomId, changeFailMessage);
                } else {
                    roomInfo.get(roomId).setUserId(targetUserId);

                    // 2.向房间所有用户发送信息
                    Map<String, Object> changeMap = new HashMap<>();
                    changeMap.put("roomInfo", roomInfo.get(roomId));

                    WsMessage<Object> changeMessage = new WsMessage<>(type, changeMap);
                    sendMessageToAll(roomId, changeMessage);
                    sendSystemMessage(roomId, targetRoomUser.getUserName()+"成为房主");

                }
                break;
            case "kick":
                /*
                 * 踢人
                 * 1.更新roomVo: realNumber-1,删除roomUser
                 * 2.删除rooms中对应的session
                 * 3.向房间所有用户发送kick信息
                 * 4.更新room、roomUser表: realNumber-1,删除roomUser
                 */
                String target = json.get("target").getAsString();

                // 1.更新roomVo: realNumber-1,删除roomUser
                roomInfo.get(roomId).setRealNumber(roomInfo.get(roomId).getRealNumber() - 1);
                UserEntity targetUser = userService.getUserByUserId(Integer.parseInt(target));

                roomUser.get(roomId).removeIf(x -> target.equals(x.getUserId().toString()));

                // 3.向房间所有用户发送logout信息
                Map<String, Object> kickMap = new HashMap<>();
                kickMap.put("roomInfo", roomInfo.get(roomId));
                kickMap.put("roomUsers", roomUser.get(roomId));

                WsMessage<Object> kickMessage = new WsMessage<>(type, kickMap);
                sendMessageToAll(roomId, kickMessage);
                sendSystemMessage(roomId, targetUser.getUserName()+"被踢出房间");

                // c.房间还在匹配中，则减少match_type的realNumber
                MatchEntity match = redisUtils.get("match_" + roomInfo.get(roomId).getType(), MatchEntity.class);
                if (match != null && roomId.equals(match.getRoomId().toString())) {
                    match.setRealNumber(roomInfo.get(roomId).getRealNumber());
                    redisUtils.set("match_" + roomInfo.get(roomId).getType(), match, 3 * 60 * 60);
                } else {
                    // d.房间不在匹配，则减少train_match表中的realNumber
                    matchService.updateRealNumber(Integer.parseInt(roomId), roomInfo.get(roomId).getRealNumber());
                }

//                roomRegistry.get(roomId).remove(userRegistry.get(target));
//                userRegistry.remove(target);

                break;
            case "chat":
                /*
                 * 聊天
                 * 1.根据userId获取user信息
                 * 2.封装聊天信息
                 */
                Map<String, Object> map = new HashMap<>();
                map.put("userId", user.getUserId());
                map.put("userName", user.getUserName());
                map.put("userImg", user.getUserImg());
                map.put("content", json.get("data").getAsString());

                WsMessage<Object> chatMessage = new WsMessage<>(type, map);
                sendMessageToAll(roomId, chatMessage);
                break;
            case "ready":
                /*
                 * 准备
                 * 1.更新redis: 用户状态 userState=1
                 * 2.发送准备信息ready给房间所有的人
                 * 3.更新room_user表：用户状态userState=1
                 */
                // 1.更新redis: 用户状态 userState=1
                roomUser.get(roomId).forEach(
                        x -> {
                            if (userId.equals(x.getUserId().toString())) { x.setUserState(1); }
                        }
                );

                // 2.发送准备信息ready给房间所有的人
                Map<String, Object> readyMap = new HashMap<>();
                readyMap.put("roomInfo", roomInfo.get(roomId));
                readyMap.put("roomUsers", roomUser.get(roomId));

                WsMessage<Object> readyMessage = new WsMessage<>(type, readyMap);
                sendMessageToAll(roomId, readyMessage);
                sendSystemMessage(roomId, user.getUserName()+"准备好了");

                break;

            case "noReady":
                /*
                 * 准备
                 * 1.更新redis: 用户状态 userState=0
                 * 2.发送准备信息ready给房间所有的人
                 * 3.更新room_user表：用户状态userState=0
                 */
                // 1.更新redis: 用户状态 userState=0
                roomUser.get(roomId).forEach(
                        x -> {
                            if (userId.equals(x.getUserId().toString())) { x.setUserState(0); }
                        }
                );

                // 2.发送准备信息ready给房间所有的人
                Map<String, Object> noReadyMap = new HashMap<>();
                noReadyMap.put("roomInfo", roomInfo.get(roomId));
                noReadyMap.put("roomUsers", roomUser.get(roomId));

                WsMessage<Object> noReadyMessage = new WsMessage<>(type, noReadyMap);
                sendMessageToAll(roomId, noReadyMessage);
                sendSystemMessage(roomId, user.getUserName()+"取消准备了");

                break;

            case "start":
                /*
                 * 开始训练
                 * 1.检查房间内用户是否全部都准备：从redis取room检查userState
                 * 2.未全部准备则发送 startFail
                 * 3.全部准备：修改roomState
                 */
                resultNum.put(roomId, 0);
                // 1.检查房间内用户是否全部都准备：从redis取room检查
                boolean isAllReady = true;
                for (RoomUserEntity roomUserEntity : roomUser.get(roomId)) {
                    if (roomUserEntity.getUserState() == 0) {
                        isAllReady = false;
                        break;
                    }
                }

                // 2.全部准备
                if (isAllReady) {
                    // 修改roomState
                    roomInfo.get(roomId).setRoomState(1);
                    roomInfo.get(roomId).setIsMatch(0);
                    book.put(roomId, bookService.getDefaultBook(roomInfo.get(roomId).getType(), roomInfo.get(roomId).getArea()));

                    // 发送信息
                    Map<String, Object> startSuccessMap = new HashMap<>();
                    startSuccessMap.put("roomInfo", roomInfo.get(roomId));
                    startSuccessMap.put("roomUsers", roomUser.get(roomId));
                    startSuccessMap.put("book", book.get(roomId));

                    WsMessage<Object> startSuccessMessage = new WsMessage<>("startSuccess", startSuccessMap);
                    sendMessageToAll(roomId, startSuccessMessage);
                    sendSystemMessage(roomId, "开始训练！！！");

                    startRoom(roomId);

                    // 其他直接开启总倒计时
                    scheduledTask.startScheduledTaskBySecond(600, new Runnable() {
                        @Override
                        public void run() {
                            WsMessage<Object> destory = new WsMessage<>("destory");
                            try {
                                sendMessageToAll(roomId, destory);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    });

                } else {    // 未全部准备则发送 startFail
                    WsMessage<Object> startFailMessage = new WsMessage<>("startFail");
                    sendMessageToAll(roomId, startFailMessage);
                    sendSystemMessage(roomId, "开始训练失败，请所有组员尽快准备！！！");
                }
                break;
            case "destory":
                /*
                 * 房主销毁房间
                 * 1.将destory信息发送给房间所有的用户
                 * 2.将rooms中room删掉
                 * 4.删除room和roomUser
                 */
                // 1.将destory信息发送给房间所有的用户
                WsMessage destoryMessage = new WsMessage<>(type);
                sendMessageToAll(roomId, destoryMessage);

                // 2.将rooms中room删掉
                roomRegistry.remove(roomId);
                clear(roomId);
                break;

            case "result":
                logger.info(json.toString());
                List<Integer> readBookList = new ArrayList<>();
                readBookList = gson.fromJson(json.get("readBookList"), readBookList.getClass());

                TrainResultEntity result = new TrainResultEntity();
                result.setRoomId(Integer.parseInt(roomId));
                result.setUserId(Integer.parseInt(userId));
                result.setBookList(readBookList.toString());
                trainResultService.save(result);
        }
    }


    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        logger.info("用户{}的websokcet出现错误: {}", userId, exception.toString());
        logger.info(exception.getMessage());
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        String roomId = (String) session.getAttributes().get("roomId");
        String userId = (String) session.getAttributes().get("userId");

        logger.info("用户{}关闭websokcet的code: {}", userId, closeStatus.getCode());
        userRegistry.remove(userId);
        roomRegistry.get(roomId).remove(session);
        if (roomRegistry.get(roomId).isEmpty()) {
            roomRegistry.remove(roomId);
            if (roomInfo.get(roomId) != null && roomInfo.get(roomId).getRealNumber() > 0) {
                if (roomInfo.get(roomId).getRoomState() == 0) {
                    // c.房间还在匹配中，则减少match_type的realNumber
                    MatchEntity match = redisUtils.get("match_" + roomInfo.get(roomId).getType(), MatchEntity.class);
                    if (match != null && roomId.equals(match.getRoomId().toString())) {
                        match.setRealNumber(roomInfo.get(roomId).getRealNumber());
                        redisUtils.set("match_" + roomInfo.get(roomId).getType(), match, 3 * 60 * 60);
                    } else {
                        // d.房间不在匹配，则减少train_match表中的realNumber
                        matchService.updateRealNumber(Integer.parseInt(roomId), roomInfo.get(roomId).getRealNumber());
                    }
                }
                clear(roomId);
            }
        }
        if (closeStatus.getCode() == 1006 && !roomRegistry.get(roomId).isEmpty()
                && userId.equals(roomInfo.get(roomId).getUserId().toString())) {
            // 更换房主
            String newHost = "";
            List<RoomUserEntity> roomUserList = roomUser.get(roomId);
            for (RoomUserEntity roomUserEntity : roomUserList) {
                if (!userId.equals(roomUserEntity.getUserId().toString())) {
                    newHost = roomUserEntity.getUserName();
                    roomInfo.get(roomId).setUserId(roomUserEntity.getUserId());
                    break;
                }
            }

            // 发送更换信息
            Map<String, Object> changeMap = new HashMap<>();
            changeMap.put("roomInfo", roomInfo.get(roomId));

            WsMessage<Object> changeMessage = new WsMessage<>("change", changeMap);
            sendMessageToAll(roomId, changeMessage);
            sendSystemMessage(roomId, newHost+"成为房主");
        }
    }
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    private RoomUserEntity createRoomUser(String roomId, String userId) {
        UserEntity user = userService.getUserByUserId(Integer.parseInt(userId));
        RoomUserEntity roomUser = new RoomUserEntity();
        roomUser.setUserId(user.getUserId());
        roomUser.setUserName(user.getUserName());
        roomUser.setUserImg(user.getUserImg());
        roomUser.setUserState(0);
        roomUser.setRoomId(Integer.parseInt(roomId));
        return roomUser;
    }

    void startRoom(String roomId) {
        roomService.updateById(roomInfo.get(roomId));
        matchService.removeByRoomId(Integer.parseInt(roomId));

        MatchEntity match = redisUtils.get("match_"+roomInfo.get(roomId).getType(), MatchEntity.class);
        if (match == null || roomId.equals(match.getRoomId().toString())) {
            MatchEntity newMatch = matchService.getMatch(roomInfo.get(roomId).getType());
            redisUtils.set("match_"+roomInfo.get(roomId).getType(), newMatch, 3*60*60);
        }
    }


    private void getResult(String roomId) {
        List<TrainResultEntity> resultList = new ArrayList<>();
        for (RoomUserEntity roomUser : roomUser.get(roomId)) {
            Integer userId = roomUser.getUserId();
            TrainResultEntity result = new TrainResultEntity();
            result.setRoomId(Integer.parseInt(roomId));
            result.setUserId(userId);
            result.setBookList(bookList.get(roomId).get(userId.toString()));
            resultList.add(result);
        }
        trainResultService.saveBatch(resultList);
    }
    void clear(String roomId) {
        roomInfo.remove(roomId);
        roomUser.remove(roomId);
        book.remove(roomId);

        resultNum.remove(roomId);
        bookList.remove(roomId);
    }

    // 发送信息给房间所有用户
    public void sendMessageToAll(String roomId, WsMessage wsMessage) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        TextMessage textMessage = new TextMessage(objectMapper.writeValueAsString(wsMessage));
        roomRegistry.get(roomId).forEach(
                x->{
                    try {
                        x.sendMessage(textMessage);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
        );
    }

    public void sendMessageToOne(String userId, WsMessage wsMessage) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        TextMessage textMessage = new TextMessage(objectMapper.writeValueAsString(wsMessage));

        userRegistry.get(userId).sendMessage(textMessage);
    }
    // 发送信息给房主

    public void sendSystemMessage(String roomId, String content) throws JsonProcessingException {
        Map<String, Object> map = new HashMap<>();
        map.put("userId", -1);
        map.put("content", content);
        WsMessage message = new WsMessage("chat", map);
        sendMessageToAll(roomId, message);
    }


    // setBean方法
    @Autowired
    public void setUserService(UserServiceImpl userService) {
        StructureTrainWebSocketHandler.userService = userService;}
    @Autowired
    public void setRoomService(RoomServiceImpl roomService) {
        StructureTrainWebSocketHandler.roomService = roomService;}
    @Autowired
    public void setBookService(BookServiceImpl bookService) { StructureTrainWebSocketHandler.bookService = bookService;}
    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) { StructureTrainWebSocketHandler.redisUtils = redisUtils; }
    @Autowired
    public void setScheduledTask(ScheduledTask scheduledTask) { StructureTrainWebSocketHandler.scheduledTask = scheduledTask; }

    @Autowired
    public void setMatchService(MatchService matchService) { StructureTrainWebSocketHandler.matchService = matchService; }

    @Autowired
    public void setTrainResultService(TrainResultServiceImpl trainResultService) { StructureTrainWebSocketHandler.trainResultService = trainResultService; }
}
